
import { isEqual, uniqueId } from 'lodash';
import * as React from 'react';
import loadjs from 'loadjs';
import classnames from 'classnames';
import './index.less';

export default class D3Tree extends React.Component {
    totalNodes = 0;
    maxLabelLength = 0;
    i = 0;
    selectedPath = [];
    overedPath = [];
    static defaultProps = {
        widthToggle: true,
        showRight: true
    }
    constructor(props) {
        super(props);
        this.state = {
            duration: 750,
            hoverNode: undefined,
            id: `d3TreeContainer_${new Date().getTime()}_${uniqueId()}`,
            layout: '0',
            panBoundary: 20,
            panSpeed: 200,

        }
        this.didMountTree = false;
    }
    render() {
        return (
            <div className={classnames("d3-tree-wrapper", { "with-right": this.props.showRight })}>
                {/* {this.props.widthToggle && <div key="1" className="d3-tree-toggle">
                    <Button.Group>
                        <Button className="opt-btn">{getLabel('502275','新建路径') }</Button>
                        <Button className="opt-btn">{getLabel('502276','导入路径') }</Button>
                    </Button.Group>
                    <Radio.Group defaultValue="0" onChange={this.changeLayout}>
                        <Radio.Button value="0">{getLabel('502277','左') }</Radio.Button>
                        <Radio.Button value="2">{getLabel('502278','右') }</Radio.Button>
                        <Radio.Button value="1">{getLabel('502279','上') }</Radio.Button>
                        <Radio.Button value="3">{getLabel('502280','下') }</Radio.Button>
                    </Radio.Group>
                </div >} */}
                <div
                    key="2"
                    ref={ref => this.container = ref}
                    id={this.state.id}
                    className="d3-tree"
                    style={this.props.widthToggle ? {} : { height: 'calc(100% - 32px)' }}
                />
                <div className="right">
                    {this.renderRight()}
                </div>
                {this.props.renderTips && <div
                    className="d3-tree-tips"
                    onMouseOver={this.diableMouseOut}
                    onMouseOut={this.enableMouseOut}
                    style={{ display: this.state.hoverNode ? 'block' : 'none' }}>
                    {this.renderTips()}
                </div>}
            </div>
        )
    }
    renderTips = () => {
        const { hoverNode } = this.state;
        if (hoverNode && this.props.renderTips) {
            return this.props.renderTips(hoverNode);
        } else {
            return null;
        }
    }
    renderRight = ()=>{
        if(typeof this.props.renderRight === 'function') {
            if (this.selectedNode && this.props.renderRight) {
                return this.props.renderRight(this.selectedNode);
            } else {
                return null;
            }
        } else {
            return this.props.renderRight;
        }
    }
    diableMouseOut = () => {
        this.canTriggerMouseOut = false;
    }
    enableMouseOut = () => {
        this.canTriggerMouseOut = true;
        this.outNode();
    }
    changeLayout = (e) => {
        this.setState({ layout: e.target.value }, this.toggleLayout);
    }
    componentWillReceiveProps(nextProps) {
        if (!isEqual(this.props.data, nextProps.data)) {
            if (nextProps.data) {
                this.initTree(nextProps.data);
            }
        }
    }
    componentWillUnmount() {
        window.removeEventListener('resize', this.resize);
    }
    componentDidMount() {
        window.addEventListener('resize', this.resize);
        if (!loadjs.isDefined('weaEdcD3js')) {
            loadjs(['/edc/d3/d3.v3.js'], 'weaEdcD3js', {
                success: () => {
                    if (this.container && this.props.data) {
                        this.initTree(this.props.data);
                        this.resize();
                    }
                }
            });
        } else {
            if (this.container && this.props.data) {
                this.initTree(this.props.data);
                this.resize();
            }
        }

    }
    toggleLayout = () => {
        this.didMountTree = false;
        if (this.container) {
            this.container.innerHTML = "";
        }
        if (this.props.data) {
            this.initTree(this.props.data);
        }
    }
    resize = () => {
        clearTimeout(this.resizeTimer);
        this.resizeTimer = setTimeout(() => {
            const viewerHeight = this.container.clientHeight;
            const viewerWidth = this.container.clientWidth;
            window.d3.select(`#${this.state.id}>svg`)
                .attr("width", viewerWidth)
                .attr("height", viewerHeight);
        }, 100);
    }
    initTree(treeData) {
        const { layout } = this.state;
        if (this.container && window.d3) {
            const viewerHeight = this.container.clientHeight;
            const viewerWidth = this.container.clientWidth;
            this.tree = window.d3.layout.tree().size([viewerHeight, viewerWidth]);
            // define a d3 diagonal projection for use by the node paths later on.
            this.diagonal = window.d3.svg.diagonal()
                .projection((d) => {
                    return (layout === '1' || layout === '3') ? [d.x, d.y] : [d.y, d.x];
                });
            // Call visit function to establish maxLabelLength
            this.visit(treeData, (d) => {
                this.totalNodes++;
                this.maxLabelLength = Math.max(d.name.replace(/[\u0391-\uFFE5]/g, "aa").length, this.maxLabelLength);

            }, (d) => {
                return d.children && d.children.length > 0 ? d.children : null;
            });

            this.sortTree();
            if (!this.didMountTree) {
                const zoom = (e) => {
                    this.svgGroup.attr("transform", "translate(" + window.d3.event.translate + ")scale(" + window.d3.event.scale + ")");
                }
                this.zoomListener = window.d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);

                // define the baseSvg, attaching a class for styling and the zoomListener
                const baseSvg = window.d3.select("#" + this.state.id).append("svg")
                    .attr("width", viewerWidth)
                    .attr("height", viewerHeight)
                    .attr("class", "overlay")
                    .call(this.zoomListener);
                this.svgGroup = baseSvg.append("g");
            }
            // Define the drag listeners for drag/drop behaviour of nodes.
            // Define the root
            const { x0 = viewerHeight / 2, y0 = 0 } = this.root || {};
            this.root = treeData;
            this.root.x0 = x0
            this.root.y0 = y0;

            // Layout the tree initially and center on the root node.
            this.update(this.root);
            if (!this.didMountTree) {
                this.centerNode(this.root);
            }
            this.didMountTree = true;
        } else {
            setTimeout(() => {
                this.initTree(treeData);
            }, 50);
        }
    }

    centerNode = (source, first) => {
        this.diabledOver = true;
        const { layout } = this.state;
        const viewerHeight = this.container.clientHeight;
        const viewerWidth = this.container.clientWidth;
        const scale = this.zoomListener.scale();
        const isRoot = source === this.root;
        let x = 0;
        let y = 0;
        if (layout === '1') {
            y = -source.y0;
            x = -source.x0;
            x = x * scale + viewerWidth / 2;
            if (isRoot) {
                y = y * scale + 100;
            } else {
                y = y * scale + viewerHeight / 2;
            }
        } else if (layout === '3') {
            y = -source.y0;
            x = -source.x0;
            x = x * scale + viewerWidth / 2;
            if (isRoot) {
                y = y * scale + viewerHeight - 100;
            } else {
                y = y * scale + viewerHeight / 2;
            }
        } else if (layout === '2') {
            x = -source.y0;
            y = -source.x0;
            if (isRoot) {
                x = x * scale + viewerWidth - 100;
            } else {
                x = x * scale + viewerWidth / 2;
            }
            y = y * scale + viewerHeight / 2;
        } else {
            x = -source.y0;
            y = -source.x0;
            if (isRoot) {
                x = x * scale + 100;
            } else {
                x = x * scale + viewerWidth / 2;
            }
            y = y * scale + viewerHeight / 2;
        }

        this.svgGroup.transition()
            .duration(this.state.duration)
            .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
        this.zoomListener.scale(scale);
        this.zoomListener.translate([x, y]);
        setTimeout(() => {
            this.diabledOver = false;
        }, this.state.duration * 2);
    }
    collapse = (d) => {
        if (d.children) {
            d._children = d.children;
            d._children.forEach(this.collapse);
            d.children = null;
        }
    }

    expand = (d) => {
        if (d._children) {
            d.children = d._children;
            d.children.forEach(this.expand);
            d._children = null;
        }
    }
    sortTree = () => {
        this.tree.sort((a, b) => {
            return b.name.toLowerCase() < a.name.toLowerCase() ? 1 : -1;
        });
    }
    visit = (parent, visitFn, childrenFn) => {
        if (!parent) {
            return;
        }
        visitFn(parent);
        const children = childrenFn(parent);
        if (children) {
            const count = children.length;
            for (let i = 0; i < count; i++) {
                this.visit(children[i], visitFn, childrenFn);
            }
        }
    }


    update = (source) => {
        // Compute the new height, function counts total children of root node and sets tree height accordingly.
        // This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed
        // This makes the layout more consistent.
        const viewerHeight = this.container.clientHeight;
        const viewerWidth = this.container.clientWidth;
        const { duration, layout } = this.state;


        const levelWidth = [1];
        const childCount = (level, n) => {
            if (n.children && n.children.length > 0) {
                if (levelWidth.length <= level + 1) {
                    levelWidth.push(0);
                }

                levelWidth[level + 1] += n.children.length;
                n.children.forEach(function (d) {
                    childCount(level + 1, d);
                });
            }
        };
        childCount(0, this.root);
        var newHeight = window.d3.max(levelWidth) * 40; // 25 pixels per line  
        this.tree = this.tree.size([newHeight, viewerWidth]);
        // Compute the new tree layout.
        this.nodes = this.tree.nodes(this.root).reverse();
        const links = this.tree.links(this.nodes);

        this.nodes.forEach((d) => {
            d.y = (d.depth * (this.maxLabelLength * ((layout === '3' || layout === '2') ? -10 : 10))); //maxLabelLength * 10px
            // alternatively to keep a fixed scale one can set a fixed depth per level
            // Normalize for fixed-depth by commenting out below line
            // d.y = (d.depth * 500); //500px per level.
        });
        const node = this.svgGroup.selectAll("g.node").data(this.nodes, (d) => {
            return d.id;
        });

        // Enter any new nodes at the parent's previous position.
        const nodeEnter = node.enter().append("g")
            .attr("class", "node")
            .attr("transform", (d) => {
                if (layout === '1' || layout === '3') {
                    return "translate(" + source.x0 + "," + source.y0 + ")";
                } else {
                    return "translate(" + source.y0 + "," + source.x0 + ")";
                }
            })
            .on('click', this.click)
            .on("mouseover", this.overNode)
            .on("mouseout", this.outNode);
        nodeEnter.append("circle")
            .attr('class', 'nodeCircle')
            .attr("r", 0)
            .style("fill", (d) => {
                return d._children ? "lightsteelblue" : "#fff";
            })

        nodeEnter.append("text")
            .attr((layout === '1' || layout === '3') ? "y" : "x", function (d) {

                return 12;
            })
            .attr("dy", ".35em")
            .attr('class', 'nodeText')
            .attr("text-anchor", (d) => {
                return d.children || d._children ? "end" : "start";
            }).text((d) => {
                return d.name;
            }).style("fill-opacity", 0);

        // phantom node to give us mouseover in a radius around it
        nodeEnter.append("circle")
            .attr('class', 'ghostCircle')
            .attr("r", 30)
            .attr("opacity", 0.2) // change this to zero to hide the target area
            .style("fill", "red");



        // Update the text to reflect whether node has children or not.
        node.select('text')
            .attr((layout === '1' || layout === '3') ? "y" : "x", (d) => {
                return 12;
            })
            .attr("text-anchor", (d) => {
                return d.children || d._children ? "end" : "start";
            })
            .text((d) => {
                return d.name;
            })
            .attr("writing-mode", (layout === '1' || layout === '3') ? "tb" : "")
            .on('click', (d) => {
                if (this.props.onClick) {
                    this.props.onClick(d)
                }
                if (window.event && window.event.stopPropagation) {
                    window.event.stopPropagation()
                } else {
                    return false;
                }

            })
            .attr("title", d => d.name)

        const that = this;
        node.select('text').attr((layout === '1' || layout === '3') ? "y" : "x", function (d) {
            const { width, height } = this.getClientRects()[0];
            const scale = that.zoomListener.scale();
            if (layout === '0' || layout === '1') {
                return d.children || d._children ? -12 : 12;
            } else if (layout === '2') {
                return (d.children || d._children ? 1 : -1) * (width / scale + 12);
            } else {
                return (d.children || d._children ? 1 : -1) * (height / scale + 12);
            }
        })
        // Change the circle fill depending on whether it has children and is collapsed
        node.select("circle.nodeCircle")
            .attr("r", 4.5)
            .style("fill", (d) => {
                return d._children ? "lightsteelblue" : "#fff";
            });

        // Transition nodes to their new position.
        var nodeUpdate = node.transition()
            .duration(duration)
            .attr("transform", (d) => {
                if (layout === '1' || layout === '3') {
                    return "translate(" + d.x + "," + d.y + ")";
                } else {
                    return "translate(" + d.y + "," + d.x + ")";
                }
            });

        // Fade the text in
        nodeUpdate.select("text")
            .style("fill-opacity", 1);

        // Transition exiting nodes to the parent's new position.
        var nodeExit = node.exit().transition()
            .duration(duration)
            .attr("transform", (d) => {
                if ((layout === '1' || layout === '3')) {
                    return "translate(" + source.x + "," + source.y + ")";
                } else {
                    return "translate(" + source.y + "," + source.x + ")";
                }
            })
            .remove();

        nodeExit.select("circle")
            .attr("r", 0);

        nodeExit.select("text")
            .style("fill-opacity", 0);

        // Update the links…
        const link = this.svgGroup.selectAll("path.link")
            .data(links, (d) => {
                return d.target.id;
            }).attr('class', d => {
                const { source: { id: from }, target: { id: to } } = d;
                if (this.selectedPath.indexOf(`${from},${to}`) > -1) {
                    return "link link-selected"
                } else if (this.overedPath.indexOf(`${from},${to}`) > -1) {
                    return "link link-over";
                }
                return "link"
            });

        // Enter any new links at the parent's previous position.
        link.enter().insert("path", "g")
            .attr("class", (d) => {
                return "link";
            })
            .attr("d", (d) => {
                const o = {
                    x: source.x0,
                    y: source.y0
                };
                return this.diagonal({
                    source: o,
                    target: o
                });
            });
        // Transition links to their new position.
        link.transition()
            .duration(duration)
            .attr("d", this.diagonal);

        // Transition exiting nodes to the parent's new position.
        link.exit().transition()
            .duration(duration)
            .attr("d", (d) => {
                var o = {
                    x: source.x,
                    y: source.y
                };
                return this.diagonal({
                    source: o,
                    target: o
                });
            })
            .remove();

        // Stash the old positions for transition.
        this.nodes.forEach((d) => {
            d.x0 = d.x;
            d.y0 = d.y;
        });
    }
    click = (d) => {
        this.selectedPath = [];
        let current = d;
        while (current) {
            this.selectedPath.push(`${current.parentid},${current.id}`);
            current = current.parent;
        }
        d = this.toggleChildren(d);
        this.update(d);
        // this.centerNode(d);

    }
    overNode = (d) => {
        /* if (!this.diabledOver) {
            clearTimeout(this.clearOveredPath);
            this.overedPath = [];
            let current = d;
            while (current) {
                this.overedPath.push(`${current.parentid},${current.id}`);
                current = current.parent;
            }
            this.update(d);
            const clear = () => {
                this.overedPath = [];
                this.update(d);
            }
            const that = this;
            clearTimeout(this.clearOveredPath);
            this.clearOveredPath = setTimeout(() => {
                clear.call(that);
            }, 700);
        } */
        clearTimeout(this.outNodeTimer)
        const node = { ...d };
        node.event = window.d3.event;
        this.setState({
            hoverNode: node
        });
    }
    outNode = () => {
        clearTimeout(this.outNodeTimer)
        this.outNodeTimer = setTimeout(() => {
            if (this.canTriggerMouseOut) {
                this.setState({
                    hoverNode: undefined
                });
            }
        }, 200);

    }
    toggleChildren = (d) => {
        if (d.children) {
            d._children = d.children;
            d.children = null;
        } else if (d._children) {
            d.children = d._children;
            d._children = null;
        }
        return d;
    }

    overCircle = (d) => {
        this.selectedNode = d;
    }
    outCircle = (d) => {
        this.selectedNode = null;
    }

}